#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <math.h>
#include "channel.h"
#include "redis.h"
#include "bcd.h"
#include "z_logger.h"
#include "dlt645_protocol.h"
#include "dlt645_acquire.h"

void dlt645_decode(device_t *device, thing_model_t *tag, const byte *dest, value_t *value) {
    int64_t lval = 0L;

    value_set_null(value);

    switch (tag->data_type) {
        case dt_bcd_integer:
        case dt_bcd_decimal:
            lval = read_int(dest, tag->digit);
            break;
        case dt_bcd_unsigned_integer:
        case dt_bcd_unsigned_decimal:
            lval = read_unsigned_int(dest, tag->digit);
            break;
        default:
            break;
    }
    value_set_long(value, lval);

    double bb = 1.0f;
    if (tag->mul_u) {
        bb *= (device->u0 * device->ubb);
    }
    if (tag->mul_i) {
        bb *= (device->i0 * device->ibb);
    }

    double scale = tag->scale;
    if (0.0f == scale) {
        scale = 1.0f;
    }

    double x = lval / pow(10, tag->decimal);
    value->value = x * bb / scale + tag->adjust;
}

static _Bool _send_dlt645_packet(channel_t *channel, dlt645_t *pkg) {
    ssize_t size;
    uint8_t buf[10 + DLT645_READ_PKG_SIZE + 2];
    uint8_t *ps = buf, *pe;

    size = encode_dlt645(buf, sizeof(buf), pkg);

    if (size < 0)
        return false;

    pe = buf + size;
    while (ps < pe) {
        size = channel_write(channel, ps, pe - ps);
        if (size < 0)
            return false;

        if (size > 0) {
            ps += size;
        }
    }

    return true;
}

static _Bool _recv_dlt645_packet(channel_t *channel, dlt645_t *pkg, int timeout) {
    dlt645_t *ret = NULL;
    ssize_t size;
    int sleep_time = 10, over_time = timeout > 500 ? timeout : 500;
    uint8_t buf[4 + 10 + DLT645_READ_PKG_SIZE + 2];
    uint8_t *ps = buf;
    uint8_t *pe = ps + sizeof(buf);

    do {
        usleep(sleep_time * 1000);
        over_time -= sleep_time;
        if (over_time < 0)
            return false;

        size = channel_read(channel, ps, pe - ps);
        if (size < 0)
            return false;

        if (size > 0) {
            ps += size;

            ret = decode_dlt645(buf, ps - buf, pkg, NULL);
        }
    } while (NULL == ret && ps < pe);

    return NULL != ret;
}

int dlt645_init(channel_t *channel, device_t *device) {
    //已经初始化，直接返回
    if (device->_stat.initialized) {
        return 0;
    }

    if (0 == device->u0)
        device->u0 = 1;
    if (0 == device->i0)
        device->i0 = 1;
    if (0 == device->ubb)
        device->ubb = 1;
    if (0 == device->ibb)
        device->ibb = 1;

    //获取电表时间，侦测电表是否在线，以便发送后续指令
    int rc = -1;
    dlt645_t req, resp;
    switch (device->_product->protocol) {
        case cp_dlt645_97:
            create_read_data_req(&req, device->meter_addr, cp_dlt645_97, 0xc011);
            rc = dlt645_execute(channel, &req, &resp, channel->timeout);
            break;
        case cp_dlt645_07:
            create_read_data_req(&req, device->meter_addr, cp_dlt645_07, 0x04000101);
            rc = dlt645_execute(channel, &req, &resp, channel->timeout);
            break;
        default:
            break;
    }

    if (rc >= 0) {
        device->_stat.initialized = true;
    }

    return rc;
}

int dlt645_execute(channel_t *channel, dlt645_t *req, dlt645_t *resp, int timeout) {
    if (!_send_dlt645_packet(channel, req)) {
        return -1;
    }

    if (!_recv_dlt645_packet(channel, resp, timeout)) {
        return -1;
    }

    return 0;
}

int dlt645_collect(channel_t *channel, device_t *device, thing_model_t *tag) {
    int rc;
    dlt645_t req, resp;

    log_trace("address=%02x%02x%02x%02x%02x%02x, di=%08x",
              device->meter_addr[0], device->meter_addr[1], device->meter_addr[2],
              device->meter_addr[3], device->meter_addr[4], device->meter_addr[5],
              tag->data_identifier);

    create_read_data_req(&req, device->meter_addr, device->_product->protocol, tag->data_identifier);

    rc = dlt645_execute(channel, &req, &resp, channel->timeout);
    if (rc < 0) {
        return rc;
    }

    if (!resp.head.ctrl_code.exception) {
        value_t value;
        unsigned char *p = resp.data;

        switch (device->_product->protocol) {
            case cp_dlt645_97:
                p += 2;
                break;
            case cp_dlt645_07:
                p += 4;
                break;
            default:
                return 0;
        }

        dlt645_decode(device, tag, p, &value);
        if (0 == tag->time_tag) {
            time(&value.timestamp);
        }
        redis_set_value(device, tag, &value);
        redis_pub_value(channel, device, tag, &value);
    }

    return 0;
}